/*	This file is part of LSAC.

	LSAC is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	LSAC is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with LSAC.  If not, see <http://www.gnu.org/licenses/>.

	Copyright (c) 2010 LEPESME "Jiboo" Jean-Baptiste
*/

#include <cassert>
#include <cstdlib>

#include "cParser.h"
#include "lsac.h"

#include "cMain.h"
#include "cExpPOD.h"
#include "cExpVar.h"
#include "cExpCall.h"
#include "cExpAdd.h"
#include "cExpMin.h"
#include "cExpMul.h"
#include "cExpDiv.h"
#include "cExpEqual.h"
#include "cExpLesser.h"
#include "cExpGreater.h"
#include "cExpLesserEqual.h"
#include "cExpGreaterEqual.h"
#include "cFunction.h"
#include "cIf.h"
#include "cElse.h"
#include "cWhile.h"
#include "cInstDecl.h"
#include "cInstAffect.h"
#include "cInstCall.h"
#include "cInstReturn.h"
#include "cInstPrint.h"

regex cParser::dRegexType("^(neant|entier|booleen)$");
regex cParser::dRegexInt("^\\d+$");
regex cParser::dRegexBool("^(vrai|faux)$");
regex cParser::dRegexName("^[A-Za-z]+\\w*$"); //FIXME: No pattern for A-Za-z ?
//regex cParser::dRegexExpOperator("^[\\+\\-\\*\\/=<>(<=)(>=)]$");
regex cParser::dRegexExpOperator("^(\\+|\\-|\\*|\\/|=|<|>|<=|>=)$");
//regex cParser::dRegexInstOperator("^(<\\-|\\(\\))$");

void cParser::parseFile(cASTree *pTree, cLexer &pLex)
{
	assert(pTree != NULL);

	while(pLex.size() > 0)
	{
		cToken *lToken = pLex.next();

		// Si debut => main
		if(*lToken == "debut")
		{
			POP_TOKEN_EXCEPT_VAL(pLex, lDebut, "debut");

			cMain *lMain = new cMain(lDebut->dLocation);
			parseBlock(lMain, pLex);
			pTree->dBlocks.push_back(lMain);

			POP_TOKEN_EXCEPT_VAL(pLex, lFin, "fin");

			delete lDebut;
			delete lFin;
		}
		// Si type => func decl
		else if(lToken->match(dRegexType))
		{
			POP_TOKEN_EXCEPT_FORMAT(pLex, lType, dRegexType);
			POP_TOKEN_EXCEPT_FORMAT(pLex, lName, dRegexName);

			cFunction *lFunction = new cFunction(lName->dLocation, toType(lType), lName->dValue);

			POP_TOKEN_EXCEPT_VAL(pLex, lCallParrO, "(");
			while(*pLex.next() != ")")
			{
				POP_TOKEN_EXCEPT_FORMAT(pLex, lParamType, dRegexType);
				POP_TOKEN_EXCEPT_FORMAT(pLex, lParamName, dRegexName);

				cInstDecl *lParamDecl = new cInstDecl(lParamName->dLocation, lFunction, toType(lParamType), lParamName->dValue);
				lFunction->dParams.push_back(lParamDecl);

				if(*pLex.next() != ")")
				{
					POP_TOKEN_EXCEPT_VAL(pLex, lVirg, ",");
					delete lVirg;
				}

				delete lParamType;
				delete lParamName;
			}
			POP_TOKEN_EXCEPT_VAL(pLex, lCallParrF, ")");
			POP_TOKEN_EXCEPT_VAL(pLex, lDebut, "debut");


			parseBlock(lFunction, pLex);
			pTree->dBlocks.push_back(lFunction);

			POP_TOKEN_EXCEPT_VAL(pLex, lFin, "fin");

			delete lType;
			delete lName;
			delete lCallParrO;
			delete lCallParrF;
			delete lDebut;
			delete lFin;
		}
		else
		{
			TOKEN_ERROR(lToken);
		}
	}
}

void cParser::parseBlock(cBlock* pBlock, cLexer &pLex)
{
	// Quand on arrive, on est au prochain token apr�s le debut
	// Quand on sort, le prochain token doit etre le fin
	cToken *lToken;

	do
	{
		lToken = pLex.next();
		if(lToken == NULL) ERROR("Erreur, fin de fichier atteint, 'fin' manquant.");

		if(*lToken != "fin")
		{
			if(*lToken == "si")
			{
				POP_TOKEN_EXCEPT_VAL(pLex, lSi, "si");
				POP_TOKEN_EXCEPT_VAL(pLex, lLeftPar, "(");
				list<string> lDelims; lDelims.push_back(")");
				cExpression *lCondition = parseExpression(pLex, lDelims);
				POP_TOKEN_EXCEPT_VAL(pLex, lRightPar, ")");
				POP_TOKEN_EXCEPT_VAL(pLex, lDebut, "debut");

				cIf *lIf = new cIf(lSi->dLocation, pBlock, lCondition);
				parseBlock(lIf, pLex);
				pBlock->dStatements.push_back(lIf);

				POP_TOKEN_EXCEPT_VAL(pLex, lFin, "fin");

				delete lSi;
				delete lLeftPar;
				delete lRightPar;
				delete lDebut;
				delete lFin;
			}
			else if(*lToken == "sinon")
			{
				POP_TOKEN_EXCEPT_VAL(pLex, lSinon, "sinon");
				POP_TOKEN_EXCEPT_VAL(pLex, lDebut, "debut");

				cElse *lElse = new cElse(lSinon->dLocation, pBlock);
				parseBlock(lElse, pLex);
				pBlock->dStatements.push_back(lElse);

				POP_TOKEN_EXCEPT_VAL(pLex, lFin, "fin");

				delete lSinon;
				delete lDebut;
				delete lFin;
			}
			else if(*lToken == "tantque")
			{
				POP_TOKEN_EXCEPT_VAL(pLex, lTantque, "tantque");
				POP_TOKEN_EXCEPT_VAL(pLex, lLeftPar, "(");
				list<string> lDelims; lDelims.push_back(")");
				cExpression *lCondition = parseExpression(pLex, lDelims);
				POP_TOKEN_EXCEPT_VAL(pLex, lRightPar, ")");
				POP_TOKEN_EXCEPT_VAL(pLex, lDebut, "debut");

				cWhile *lWhile = new cWhile(lTantque->dLocation, pBlock, lCondition);
				parseBlock(lWhile, pLex);
				pBlock->dStatements.push_back(lWhile);

				POP_TOKEN_EXCEPT_VAL(pLex, lFin, "fin");

				delete lTantque;
				delete lLeftPar;
				delete lRightPar;
				delete lDebut;
				delete lFin;
			}
			else if(*lToken == "retourne")
			{
				POP_TOKEN_EXCEPT_VAL(pLex, lRetourne, "retourne");
				list<string> lDelims; lDelims.push_back(";");
				cExpression *lExpression = parseExpression(pLex, lDelims);
				POP_TOKEN_EXCEPT_VAL(pLex, lPV, ";");

				cInstReturn *lReturn = new cInstReturn(lRetourne->dLocation, pBlock, lExpression);
				pBlock->dStatements.push_back(lReturn);

				delete lRetourne;
				delete lPV;
			}
			else if(*lToken == "affiche")
			{
				POP_TOKEN_EXCEPT_VAL(pLex, lAffiche, "affiche");
				list<string> lDelims; lDelims.push_back(";");
				cExpression *lExpression = parseExpression(pLex, lDelims);
				POP_TOKEN_EXCEPT_VAL(pLex, lPV, ";");

				cInstPrint *lPrint = new cInstPrint(lAffiche->dLocation, pBlock, lExpression);
				pBlock->dStatements.push_back(lPrint);

				delete lAffiche;
				delete lPV;
			}
			else
			{
				// Si type => var decl
				if(lToken->match(dRegexType))
				{
					POP_TOKEN_EXCEPT_FORMAT(pLex, lType, dRegexType);
					POP_TOKEN_EXCEPT_FORMAT(pLex, lName, dRegexName);

					cInstDecl *lDecl = new cInstDecl(lName->dLocation, pBlock, toType(lType), lName->dValue);
					pBlock->dStatements.push_back(lDecl);

					POP_TOKEN_EXCEPT_VAL(pLex, lPV, ";");

					delete lType;
					delete lName;
					delete lPV;
				}
				// Si nom de var => call/affect
				else if(lToken->match(dRegexName))
				{
					POP_TOKEN_EXCEPT_FORMAT(pLex, lLValue, dRegexName);
					POP_TOKEN(pLex, lOperateur);

					if(*lOperateur == "<-")
					{
						list<string> lDelims; lDelims.push_back(";");
						cInstAffect *lAffect = new cInstAffect(lOperateur->dLocation, pBlock, lLValue->dValue, parseExpression(pLex, lDelims));
						pBlock->dStatements.push_back(lAffect);
					}
					else if(*lOperateur == "(")
					{
						cInstCall *lCall = new cInstCall(lLValue->dLocation, pBlock, lLValue->dValue);

						list<string> lDelims; lDelims.push_back(","); lDelims.push_back(")");
						while(*pLex.next() != ")")
						{
							cExpression *lParam = parseExpression(pLex, lDelims);
							lCall->dParams.push_back(lParam);

							if(*pLex.next() != ")")
							{
								POP_TOKEN_EXCEPT_VAL(pLex, lVir, ",");
								delete lVir;
							}
						}
						POP_TOKEN_EXCEPT_VAL(pLex, lParrF, ")");

						pBlock->dStatements.push_back(lCall);

						delete lParrF;
					}
					else
					{
						TOKEN_ERROR(lOperateur);
					}
					POP_TOKEN_EXCEPT_VAL(pLex, lPV, ";");

					delete lLValue;
					delete lOperateur;
					delete lPV;
				}
				else
				{
					TOKEN_ERROR(lToken);
				}
			}
		}
		lToken = pLex.next();
	}
	while(*lToken != "fin");
}

bool stringListContains(list<string> &pList, const char* pVal)
{
	list<string>::iterator lIterator = pList.begin();
	for(; lIterator != pList.end(); lIterator++)
	{
		if((*lIterator) == pVal)
		{
			return true;
		}
	}
	return false;
}

cExpression* cParser::parseExpression(cLexer &pLex, list<string> pDelims)
{
	/*
	 * 2 + 3 + 4
	 * On parse direct le 2
	 * On pop l' operateur on cr�� le node en fonction, et on le lie avec lvalue = 2 et lrvalue = parseExpression
	 *    La on arrive avec le 3.. et sadevrait marcher
	 *
	 * On boucle tant que le prochain token apr�s le "parse direct" (etape 1) est different de pDelim (sauf exception pour un call)
	 */

	// lol + 3 + mdr(3) + mdr(4);

	cExpression *lLValue = parseValue(pLex);
	cExpression *lExpression = NULL;

	if(stringListContains(pDelims, pLex.next()->dValue.c_str()))
	{
		lExpression = lLValue;
	}
	else if(stringListContains(pDelims, "("))
	{
		// Si on a une parenthese ouvrant, faut verifier qu' on est pas le delim aprés la premiere parenthese fermante
		int lIndex = 1;
		do
		{
			cToken *lToken = pLex.next(lIndex);
			if(lToken != NULL)
			{
				if(*lToken == ")")
				{
					if(stringListContains(pDelims, pLex.next(lIndex + 1)->dValue.c_str())) return lLValue;
					else break;
				}
			}
			else
			{
				ERROR("Erreur, fin de fichier atteinte, ')' manquant.");
			}
			lIndex++;
		}
		while(true); // Oui c' est moche, mais plus simple..
	}
	else
	{
		POP_TOKEN_EXCEPT_FORMAT(pLex, lOperator, dRegexExpOperator);

		if(*lOperator == "+")
		{
			lExpression = new cExpAdd(lOperator->dLocation, lLValue, parseExpression(pLex, pDelims));
		}
		else if(*lOperator == "-")
		{
			lExpression = new cExpMin(lOperator->dLocation, lLValue, parseExpression(pLex, pDelims));
		}
		else if(*lOperator == "*")
		{
			lExpression = new cExpMul(lOperator->dLocation, lLValue, parseExpression(pLex, pDelims));
		}
		else if(*lOperator == "/")
		{
			lExpression = new cExpDiv(lOperator->dLocation, lLValue, parseExpression(pLex, pDelims));
		}
		else if(*lOperator == "=")
		{
			lExpression = new cExpEqual(lOperator->dLocation, lLValue, parseExpression(pLex, pDelims));
		}
		else if(*lOperator == ">")
		{
			lExpression = new cExpGreater(lOperator->dLocation, lLValue, parseExpression(pLex, pDelims));
		}
		else if(*lOperator == "<")
		{
			lExpression = new cExpLesser(lOperator->dLocation, lLValue, parseExpression(pLex, pDelims));
		}
		else if(*lOperator == ">=")
		{
			lExpression = new cExpGreaterEqual(lOperator->dLocation, lLValue, parseExpression(pLex, pDelims));
		}
		else if(*lOperator == "<=")
		{
			lExpression = new cExpLesserEqual(lOperator->dLocation, lLValue, parseExpression(pLex, pDelims));
		}
		else
		{
			TOKEN_ERROR(lOperator);
		}

		delete lOperator;
	}

	return lExpression;
}

cExpression* cParser::parseValue(cLexer &pLex)
{
	cExpression *lExpression;
	POP_TOKEN(pLex, lToken);

	if(lToken->match(dRegexInt))
	{
		lExpression = new cExpPOD<int>(lToken->dLocation, atoi(lToken->dValue.c_str()));
	}
	else if(lToken->match(dRegexBool))
	{
		lExpression = new cExpPOD<bool>(lToken->dLocation, *lToken == "vrai" ? true : false);
	}
	else if(lToken->match(dRegexName))
	{
		if(*pLex.next() == "(")
		{
			POP_TOKEN_EXCEPT_VAL(pLex, lParrO, "(");

			lExpression = new cExpCall(lToken->dLocation, lToken->dValue);

			list<string> lDelims; lDelims.push_back(","); lDelims.push_back(")");
			while(*pLex.next() != ")")
			{
				cExpression *lParam = parseExpression(pLex, lDelims);
				((cExpCall*)lExpression)->dParams.push_back(lParam);

				if(*pLex.next() != ")")
				{
					POP_TOKEN_EXCEPT_VAL(pLex, lVir, ",");
					delete lVir;
				}
			}

			POP_TOKEN_EXCEPT_VAL(pLex, lParrF, ")");

			delete lParrO;
			delete lParrF;
		}
		else
		{
			lExpression = new cExpVar(lToken->dLocation, lToken->dValue);
		}
	}
	else
	{
		TOKEN_ERROR(lToken);
	}
	delete lToken;
	return lExpression;
}

eType cParser::toType(cToken *pToken)
{
	eType lType;
	if(!pToken->match(dRegexType)) ERROR("Erreur, trouve '" << pToken << "' alors que j' attendais '" << dRegexType.str() << "'.");

	if(*pToken == "entier")
	{
		lType = TYPE_INT;
	}
	else if(*pToken == "booleen")
	{
		lType = TYPE_BOOL;
	}
	else if(*pToken == "neant")
	{
		lType = TYPE_VOID;
	}
	else
	{
		TOKEN_ERROR(pToken);
	}
	return lType;
}
